"""
Grave! Important! Важно!

Proletoj el ĉiuj landoj, unuiĝu!
Workers of the world, unite!
Пролетарии всех стран, соединяйтесь!

https://tkom.pro
"""

from django.db import models
from django.db.models import Q
from django.db.models.fields.related_descriptors import ManyToManyDescriptor
import sys


# Абстрактный класс для наследования метода автоматического создания связанных объектов модели
class SiriusoAutomataKreinto(models.Model):

    # список зависимых моделей, которые должны быть созданы при сохранении текущего объекта в БД
    infanaj_modeloj = []
    # список автоматически созданных зависимых объектов (необходимо для использования их
    # при создании следующих объектов других связанных моделей)
    infanaj_objektoj = []

    class Meta:
        abstract = True

    def __parse_values(self, val):
        out = val
        if not isinstance(val, list):
            if isinstance(val, str):
                if not val.find('self.'):
                    out = getattr(self, val.replace('self.', ''))
                elif val == 'self':
                    out = self
            else:
                out = val
        else:
            for i in range(0, int(len(val)/2)):
                model_params = val[i].split('.')
                model = getattr(sys.modules[model_params[0] + '.models'], model_params[1])

                kwargs = {}
                for k, v in val[i + 1].items():
                    cur_val = self.__parse_values(v)
                    # Если в фильтре хоть одно из полей None,
                    # то возвращаем None
                    if cur_val is None:
                        return cur_val

                    kwargs[k] = cur_val

                out = model.objects.filter(**kwargs)

                if out.count():
                    # Если нашелся объект в БД, то он и есть наше значение
                    out = out if out.count() > 1 else out[0]
                    break
                else:
                    # Иначе идем к следующему варинату поиска значения
                    out = None
                    continue

        return out

    def __create_infan_objecto(self, model, model_fields, **kwargs):
        # Проверяем наличие подобного дочернего объекта в БД
        fnd_kwargs = {}
        fnd_kwargs.update(model_fields)

        if 'exclude' in kwargs and kwargs['exclude'] is not None:
            for exclude in kwargs['exclude']:
                if exclude in fnd_kwargs:
                    del fnd_kwargs[exclude]

        if 'extra' in kwargs and kwargs['extra'] is not None:
            fnd_kwargs.update(kwargs['extra'])

        obj = model.objects.filter(**fnd_kwargs)

        if obj.count():
            return obj if obj.count() > 1 else obj[0]

        # Сохраняем его в базу
        try:
            obj = model(**model_fields)
            obj.save()

            if 'm2m_fields' in kwargs:
                for field, val in kwargs['m2m_fields'].items():
                    if isinstance(val, models.QuerySet):
                        getattr(obj, field).set(val)
                    else:
                        getattr(obj, field).add(val)

            return obj
        except:
            return None

    def __prepare_multi_kwargs(self, multi_fields, fields, step):
        ost = step

        for field, values in multi_fields.items():
            mod = ost % len(values)
            ost = int(ost / len(values))
            fields.update({field: values[mod]})

        return fields

    def __check_conditions(self, cur):
        if 'conditions' in cur:
            conditions = cur['conditions']
            # Смотрим, какой тип условия ограницения передан
            if isinstance(conditions, Q):
                val = self.__class__.objects.filter(conditions, pk=self.pk, )
                return val.count() > 0
            elif isinstance(conditions, tuple) or isinstance(conditions, list):
                val = self.__class__.objects.filter(*conditions, pk=self.pk,)
                return val.count() > 0
            else:
                val = self.__parse_values(conditions)
                return val and val is not None

        return True

    def save(self, force_insert=False, force_update=False, using=None,
             update_fields=None):
        created = True if self.__class__.objects.filter(pk=self.pk).exists() else False

        super(SiriusoAutomataKreinto, self).save(force_insert=force_insert, force_update=force_update, using=using,
                                                 update_fields=update_fields)

        if len(self.infanaj_modeloj):
            u = 1
            for cur in self.infanaj_modeloj:
                if not self.__check_conditions(cur):
                    continue

                u = u + 1
                if 'always_create' in cur and cur['always_create'] or not created:
                    model = getattr(sys.modules[cur['app'] + '.models'], cur['model'])
                    kwargs = {cur['fk']: self}
                    m2m_fields = {}
                    is_multi = True if 'multi' in cur and cur['multi'] else False
                    multi_field = {}

                    if 'fields' in cur:
                        abort = False
                        for field, val in cur['fields'].items():
                            val = self.__parse_values(val)

                            if val is None:
                                # Если не удалось определить (однозначно определить
                                # при требуемой однозначности) обязательное поле
                                abort = True
                                break
                            elif isinstance(getattr(model, field), ManyToManyDescriptor):
                                m2m_fields[field] = val
                            elif isinstance(val, models.QuerySet):
                                if is_multi:
                                    multi_field[field] = val
                                else:
                                    abort = True
                                    break
                            else:
                                kwargs[field] = val

                        if abort:
                            # Пропускаем текущую зависимую модель без сохранения
                            self.infanaj_objektoj.append(None)
                            continue

                    extra = None
                    exclude = None

                    if 'extra' in cur and isinstance(cur['extra'], dict):
                        extra = cur['extra']

                    if 'extra_exclude' in cur:
                        exclude = cur['extra_exclude']

                    if is_multi and len(multi_field):
                        total_steps = 0
                        objects = []
                        for field, values in multi_field.items():
                            total_steps = total_steps * len(values) if total_steps else len(values)

                        for i in range(0, total_steps):
                            obj = self.__create_infan_objecto(model, self.__prepare_multi_kwargs(multi_field, kwargs, i),
                                                              m2m_fields=m2m_fields, extra=extra, exclude=exclude)
                            if not obj is None:
                                objects.append(obj)

                        self.infanaj_objektoj.append(objects)
                    else:
                        self.infanaj_objektoj.append(self.__create_infan_objecto(model, kwargs, m2m_fields=m2m_fields,
                                                                                 extra=extra, exclude=exclude))
